/*
 * Copyright 2012 Sedov Eduard.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
package org.es.registry;

import java.util.ArrayList;
import java.util.HashSet;
import java.util.LinkedList;
import java.util.List;
import java.util.Set;

/**
 *
 * @author Sedov Eduard
 */
public class LevelingRegistry implements Registry {

    protected LinkedList<Registry> registries = new LinkedList() {
        @Override
        public Object getLast() {
            if (isEmpty()) {
                throw new IllegalArgumentException("no registries added yet");
            }
            return super.getLast();
        }
    };
    protected String current = "/";
    protected RegistryListenerSupport listeners = new RegistryListenerSupport(this);

    public LevelingRegistry() {
    }

    public LevelingRegistry(List<Registry> registries) {
        this(registries.toArray(new Registry[registries.size()]));
    }

    public LevelingRegistry(Registry... registries) {
        for (Registry r : registries) {
            if (r == null) {
                throw new NullPointerException("A item in the list is null.");
            }
            this.registries.add(r);
        }
    }

    public void addRegistry(Registry registry) {
        registries.add(registry);
    }

    public void removeRegistry(Registry registry) {
        registries.remove(registry);
    }

    public List<Registry> getRegistries() {
        return new ArrayList(registries);
    }

    public void setRegistry(Registry registry, int index) {
        registries.set(index, registry);
    }

    public boolean isExist(String name) {
        for (Registry registry : registries) {
            if (registry.isExist(name)) {
                return true;
            }
        }
        return false;
    }

    public boolean isDirectory(String name) {
        name = toAbsolute(name);
        for (Registry registry : registries) {
            if (registry.isExist(name)) {
                return registry.isDirectory(name);
            }
        }
        throw new IllegalArgumentException("name not found");
    }

    public String cd() {
        listeners.fireOnCd(this, current);
        return current;
    }

    public String cd(String name) {
        name = toAbsolute(name);
        for (Registry registry : registries) {
            if (registry.isExist(name) && registry.isDirectory(name)) {
                String oldCurrent = current;
                registry.addListener(listeners);
                try{
                    current = registry.cd(name);                    
                }finally{
                    registry.removeListener(listeners);
                }
                listeners.fireOnCd(registry, oldCurrent, current);
                return current;
            }
        }
        throw new IllegalArgumentException("name not found");
    }

    public void mkdir(String name) {
        name = toAbsolute(name);
        for (Registry registry : registries) {
            if (registry.isExist(name) && registry.isDirectory(name)) {
                return;
            }
        }
        Registry writeLevelRegistry = registries.getLast();
        writeLevelRegistry.addListener(listeners);
        try {
            writeLevelRegistry.mkdir(name);
        } finally {
            writeLevelRegistry.removeListener(listeners);
        }
    }

    public void rmdir(String name) {
        name = toAbsolute(name);
        Registry writeLevelRegistry = registries.getLast();
        for (Registry registry : registries) {
            if (registry != writeLevelRegistry && registry.isExist(name) && registry.isDirectory(name)) {
                throw new IllegalArgumentException("access denied");
            }
        }
        writeLevelRegistry.addListener(listeners);
        try {
            writeLevelRegistry.rmdir(name);
            current = writeLevelRegistry.cd();
        } finally {
            writeLevelRegistry.removeListener(listeners);
        }
    }

    public <T> T get(String path) {
        path = toAbsolute(path);
        for (Registry registry : registries) {
            if (registry.isExist(path)) {
                if (registry.isDirectory(path)) {
                    throw new IllegalArgumentException("cannot get value of path because it is a directory");
                }
                registry.addListener(listeners);
                try {
                    return (T) registry.get(path);
                } finally {
                    registry.removeListener(listeners);
                }
            }
        }
        return null;
    }

    public <T> T put(String path, T value) {
        path = toAbsolute(path);
        Registry writeLevelRegistry = registries.getLast();
        for (Registry registry : registries) {
            if (registry != writeLevelRegistry && registry.isExist(path)) {
                throw new IllegalArgumentException("access denied");
            }
        }
        writeLevelRegistry.addListener(listeners);
        try {
            return (T) writeLevelRegistry.put(path, value);
        } finally {
            writeLevelRegistry.removeListener(listeners);
        }
    }

    public <T> T delete(String path) {
        path = toAbsolute(path);
        Registry writeLevelRegistry = registries.getLast();
        for (Registry registry : registries) {
            if (registry != writeLevelRegistry && registry.isExist(path)) {
                throw new IllegalArgumentException("access denied");
            }
        }
        writeLevelRegistry.addListener(listeners);
        try {
            return (T) writeLevelRegistry.delete(path);
        } finally {
            writeLevelRegistry.removeListener(listeners);
        }
    }

    public Set<String> list() {
        Set<String> result = new HashSet();
        for (Registry registry : registries) {
            result.addAll(registry.list());
        }
        listeners.fireOnList(this, result);
        return result;
    }

    public void rename(String oldName, String newName) {
        oldName = toAbsolute(oldName);
        Registry writeLevelRegistry = registries.getLast();
        for (Registry registry : registries) {
            if (registry != writeLevelRegistry && registry.isExist(oldName)) {
                throw new IllegalArgumentException("access denied");
            }
        }
        writeLevelRegistry.addListener(listeners);
        try {
            writeLevelRegistry.rename(oldName, newName);
        } finally {
            writeLevelRegistry.removeListener(listeners);
        }
    }

    /**
     * Puts the given listener to internal listener list.
     *
     * @param listener the listner to put
     */
    public void addListener(RegistryListener listener) {
        listeners.addListener(listener);
    }

    /**
     * Removes the given listener from internal listener list.
     *
     * @param listener the listener to remove
     */
    public void removeListener(RegistryListener listener) {
        listeners.removeListener(listener);
    }

    /**
     * Returns all registered listeners.
     *
     * @return a list with all registered listeners
     */
    public List<RegistryListener> getListeners() {
        return listeners.getListeners();
    }

    protected String toAbsolute(String path) {
        if (path.startsWith(PATH_DELIMITER)) {
            return path;
        }
        return current + path;
    }
}
